﻿// Copyright 2012 Jack Bulan (agentgeo)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Globalization;
using System.Security.Cryptography;
using System.Drawing;
using OpenTK;
using GemWorkshop.Formats.Images;

namespace GemWorkshop.Formats.Nsbmd
{
    public class ObjToNsbmd
    {
        // Variables
        private Dictionary<string, MaterialDef> materials = new Dictionary<string, MaterialDef>();
        private Dictionary<string, MaterialDef> textures = new Dictionary<string, MaterialDef>();
        private List<Vector4> verticies = new List<Vector4>();
        private List<Vector2> textCoords = new List<Vector2>();
        private List<Vector3> normals = new List<Vector3>();
        private bool sketchupHack = false;

        private MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        
        public ObjToNsbmd()
        { }

        /// <summary>
        /// Converts a Wavefront .obj file to a Nintendo Model.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="output"></param>
        public void Convert(string input, Stream output)
        {
            LoadObjModel(input); // Load the .obj

            BinaryWriter bw = new BinaryWriter(output);
            //bw.Write("BMD0", Encoding.ASCII, false);
            bw.Write('B'); bw.Write('M'); bw.Write('D'); bw.Write('0');
            bw.Write(new byte[] { 0xFF, 0xFE, 0x02, 0x00 }, 0, 4);
            bw.Write((Int32)0);
            bw.Write((Int16)0x10);
            bw.Write((Int16)1);
            for (int i = 0; i < 1; i++)
            {
                bw.Write((Int32)0x14);
            }
            //bw.Write("MDL0", Encoding.ASCII, false);
            bw.Write('M'); bw.Write('D'); bw.Write('L'); bw.Write('0');
            bw.Write((Int32)0);
            //Nitro 3d
            bw.Write((byte)0);
            bw.Write((byte)1);
            bw.Write((Int16)0x28);

            bw.Write((Int16)0x8);
            bw.Write((Int16)0x10);
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < 1; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)0x8);
            for (int i = 0; i < 1; i++)
            {
                bw.Write((Int32)0x30);
            }
            string name = Path.GetFileNameWithoutExtension(input);
            if (name.Length > 16)
            {
                name = name.Remove(16);
            }
            for (int i = 0; i < 1; i++)
            {
                bw.Write(Encoding.UTF8.GetBytes(name));
                if (name.Length != 16)
                {
                    bw.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            long offsetpos = bw.BaseStream.Position;
            bw.Write((Int32)0); //MDL0 Length - 0x44
            bw.Write((Int32)0); //Bones Offset - 0x44
            bw.Write((Int32)0); //Matbwials Offset - 0x44
            bw.Write((Int32)0); //Polygon Start Offset - 0x44
            bw.Write((Int32)0); //Polygon End Offset - 0x44 (MDL0 Length - 0x44)
            bw.Write((byte)0);
            bw.Write((byte)0);
            bw.Write((byte)0);
            bw.Write((byte)1);
            bw.Write((byte)materials.Count);
            bw.Write((byte)materials.Count);
            bw.Write((byte)1);
            bw.Write((byte)0);

            Vector3 m_Scale = new Vector3(1, 1, 1);
            Vector4[] scaledvtxs = new Vector4[verticies.Count];
            for (int i = 0; i < verticies.Count; i++)
                scaledvtxs[i] = new Vector4(verticies[i].X * m_Scale.X, verticies[i].Y * m_Scale.Y, verticies[i].Z * m_Scale.Z, verticies[i].W);

            int poly = 0;
            int tri = 0;
            int quat = 0;
            Vector3 Min = new Vector3();
            Vector3 Max = new Vector3();
            foreach (Vector4 v in verticies)
            {
                float x = v.X;
                float y = v.Y;
                float z = v.Z;
                if (x < Min.X || Min.X == 0) { Min.X = x; }
                if (y < Min.Y || Min.Y == 0) { Min.Y = y; }
                if (z < Min.Z || Min.Z == 0) { Min.Z = z; }
                if (x > Max.X || Max.X == 0) { Max.X = x; }
                if (y > Max.Y || Max.Y == 0) { Max.Y = y; }
                if (z > Max.Z || Max.Z == 0) { Max.Z = z; }
            }
            foreach (MaterialDef m in materials.Values)
            {
                foreach (FaceDef f in m.Faces)
                {
                    poly++;
                    switch (f.VtxIndices.Length)
                    {
                        case 1:
                            break;
                        case 2:
                            break;
                        case 3:
                            {
                                //Vector4 a = verticies[f.m_VtxIndices[0]];
                                //Vector4 b = verticies[f.m_VtxIndices[1]];
                                //Vector4 c = verticies[f.m_VtxIndices[2]];
                                tri++;
                                break;
                            }
                        case 4:
                            {
                                //Vector4 a = verticies[f.m_VtxIndices[0]];
                                //Vector4 b = verticies[f.m_VtxIndices[1]];
                                //Vector4 c = verticies[f.m_VtxIndices[2]];
                                //Vector4 d = verticies[f.m_VtxIndices[3]];
                                quat++;
                                break;
                            }
                    }
                }
            }
            float scale = 1f; uint scaleval = 0;
            float largest = new float[] { Max.X, Max.Y, Max.Z }.Max();
            while (largest > (32767f / 4096f))
            {
                scaleval++;
                scale /= 2f;
                largest /= 2f;
            }

            if (scaleval > 31)
            {
                System.Windows.Forms.MessageBox.Show("Your model is too large to be imported. Try scaling it down.");
                return;
            }
            for (int i = 0; i < scaledvtxs.Length; i++)
            {
                scaledvtxs[i].X *= scale;
                scaledvtxs[i].Y *= scale;
                scaledvtxs[i].Z *= scale;
            }
            bw.Write((Int32)(4096));
            bw.Write((Int32)4096);

            bw.Write((Int16)verticies.Count);
            bw.Write((Int16)poly);
            bw.Write((Int16)tri);
            bw.Write((Int16)quat);

            bw.Write((Int16)(Min.X * 4096));
            bw.Write((Int16)(Min.Y * 4096));
            bw.Write((Int16)(Min.Z * 4096));

            Vector3 hw = Max - Min;
            bw.Write((Int16)(hw.X * 4096));
            bw.Write((Int16)(hw.Y * 4096));
            bw.Write((Int16)(hw.Z * 4096));

            bw.Write((Int32)(4096));
            bw.Write((Int32)(4096));

            //Bones
            bw.Write((byte)0);
            bw.Write((byte)1);
            bw.Write((Int16)0x28);

            bw.Write((Int16)0x8);
            bw.Write((Int16)0x10);
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < 1; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)0x8);
            for (int i = 0; i < 1; i++)
            {
                bw.Write((Int32)0x28);
            }

            //bw.Write("Root", Encoding.ASCII, false);
            bw.Write('R'); bw.Write('o'); bw.Write('o'); bw.Write('t');
            bw.Write(new byte[12], 0, 12);

            bw.Write((byte)7);
            bw.Write((byte)0);
            bw.Write((byte)0);
            bw.Write((byte)0);

            long curpos = bw.BaseStream.Position;
            bw.BaseStream.Position = offsetpos + 4;
            bw.Write((Int32)curpos - 0x44);
            bw.BaseStream.Position = curpos;

            bw.Write((byte)0x26);//NodeDesc[001], 
            bw.Write((Int32)0);  //0(Root), 0(Root), 0x00, StoreStackID=0
            bw.Write((byte)0x2); //Node,
            bw.Write((byte)0);   //0(Root),
            bw.Write((byte)1);   //visible
            bw.Write((byte)0x0b);//PosScale[000]
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((byte)0x04);//Mat[000]
                bw.Write((byte)i);
                bw.Write((byte)0x05);//Shp
                bw.Write((byte)i);
            }
            bw.Write((byte)0x2b);//PosScale[001]
            bw.Write((byte)0x01);//Ret
            bw.Write((byte)0x00);//Nop

            curpos = bw.BaseStream.Position;
            bw.BaseStream.Position = offsetpos + 8;
            bw.Write((Int32)curpos - 0x44);
            bw.BaseStream.Position = curpos;

            //Materials
            #region Materials
            int mm = materials.Count + materials.Count;
            while (mm % 4 != 0) { mm++; }
            mm -= materials.Count;
            mm -= materials.Count;

            bw.Write((Int16)((16 + 0x18 * materials.Count) + 4)); //Tex
            bw.Write((Int16)((16 + 0x18 * materials.Count) * 2 + 4)); //Pal
            bw.Write((byte)0);
            bw.Write((byte)materials.Count);
            bw.Write((Int16)(16 + 0x18 * materials.Count));

            bw.Write((Int16)0x8);
            bw.Write((Int16)(0x8 + materials.Count * 4 + 4));
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)(0x4 + 0x4 * materials.Count));
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int32)(((16 + 0x18 * materials.Count) * 3 + 44 * i + 4) + materials.Count + materials.Count + mm));
            }
            for (int i = 0; i < materials.Count; i++)
            {
                name = materials.Keys.ToArray()[i];
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                //bw.Write(name, Encoding.ASCII, false);
                bw.Write(Encoding.UTF8.GetBytes(name));
                if (name.Length != 16)
                {
                    bw.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }

            bw.Write((byte)0);
            bw.Write((byte)materials.Count);
            bw.Write((Int16)(16 + 0x18 * materials.Count));

            bw.Write((Int16)0x8);
            bw.Write((Int16)(0x8 + materials.Count * 4 + 4));
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)(0x4 + 0x4 * materials.Count));
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)((16 + 0x18 * materials.Count) * 3 + materials.Count + i + 4));
                bw.Write((Int16)1);
            }
            for (int i = 0; i < materials.Count; i++)
            {
                name = materials.Keys.ToArray()[i];
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                //bw.Write(name, Encoding.ASCII, false);
                bw.Write(Encoding.UTF8.GetBytes(name));
                if (name.Length != 16)
                {
                    bw.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }

            bw.Write((byte)0);
            bw.Write((byte)materials.Count);
            bw.Write((Int16)(16 + 0x18 * materials.Count));

            bw.Write((Int16)0x8);
            bw.Write((Int16)(0x8 + materials.Count * 4 + 4));
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)(0x4 + 0x4 * materials.Count));
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)((16 + 0x18 * materials.Count) * 3 + i + 4));
                bw.Write((Int16)1);
            }
            for (int i = 0; i < materials.Count; i++)
            {
                name = materials.Keys.ToArray()[i] + "_pl";
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                //bw.Write(name, Encoding.ASCII, false);
                bw.Write(Encoding.UTF8.GetBytes(name));
                if (name.Length != 16)
                {
                    bw.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((byte)i);
            }
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((byte)i);
            }
            while (bw.BaseStream.Position % 4 != 0) { bw.Write((Byte)0); }
            for (int i = 0; i < materials.Count; i++)
            {
                uint alpha = (uint)(materials.Values.ToArray()[i].Opacity >> 3);
                uint polyattr = 0;//0x00000080 | (alpha << 16);
                polyattr |= 3 << 6;
                polyattr |= 1 << 11;
                polyattr |= alpha << 16;
                bw.Write((Int16)(0));
                bw.Write((Int16)(44));
                bw.Write((UInt16)(Actions.ColorToBGR15(materials.Values.ToArray()[i].DiffuseColor)));
                bw.Write((UInt16)0);
                bw.Write((UInt16)0);
                bw.Write((UInt16)0);

                bw.Write((UInt32)polyattr);
                bw.Write(new byte[] { 0xff, 0xf8, 0x1f, 0x3f }, 0, 4);
                bw.Write((UInt32)0x00030000);
                bw.Write((UInt32)0xffffffff);
                bw.Write((UInt32)0);
                bw.Write((UInt16)0);//1);
                bw.Write((UInt16)0);//1);
                bw.Write((UInt32)0);//4096);
                bw.Write((UInt32)0);//4096);
            }

            curpos = bw.BaseStream.Position;
            bw.BaseStream.Position = offsetpos + 12;
            bw.Write((Int32)curpos - 0x44);
            bw.BaseStream.Position = curpos;
            #endregion

            //Poly
            List<byte[]> dl = new List<byte[]>();
            GXDisplayListPacker dlpackbw = new GXDisplayListPacker();
            int b = 0;
            foreach (MaterialDef mat in materials.Values)
            {
                Vector2 tcscale = mat.DiffuseMapSize;

                float largesttc = 0f;
                foreach (FaceDef face in mat.Faces)
                {
                    foreach (int txci in face.TxcIndices)
                    {
                        if (txci < 0) continue;
                        Vector2 txc = Vector2.Multiply(textCoords[txci], tcscale);
                        if (Math.Abs(txc.X) > largesttc) largesttc = Math.Abs(txc.X);
                        if (Math.Abs(txc.Y) > largesttc) largesttc = Math.Abs(txc.Y);
                    }
                }
                float _tcscale = largesttc / (32767f / 16f);
                if (_tcscale > 1f)
                {
                    _tcscale = (float)Math.Ceiling(_tcscale * 4096f) / 4096f;
                    mat.TexCoordScale = _tcscale;
                    Vector2.Divide(ref tcscale, _tcscale, out tcscale);
                }
                else
                    mat.TexCoordScale = 0f;

                dlpackbw.ClearCommands();
                int lastface = -1;
                Vector4 lastvtx = new Vector4(0f, 0f, 0f, 12345678f);
                foreach (FaceDef face in mat.Faces)
                {
                    int nvtx = face.VtxIndices.Length;

                    if (nvtx != lastface || lastface > 4)
                    {
                        uint vtxtype = 0;
                        switch (nvtx)
                        {
                            case 1:
                            case 2:
                            case 3: vtxtype = 0; break;
                            case 4: vtxtype = 1; break;
                            default: vtxtype = 2; break;
                        }

                        if (lastface != -1) dlpackbw.AddCommand(0x41);
                        dlpackbw.AddCommand(0x40, vtxtype);
                        if (lastface == -1) dlpackbw.AddCommand(0x14, 0);

                        lastface = nvtx;
                    }

                    dlpackbw.AddColorCommand(mat.DiffuseColor);

                    switch (nvtx)
                    {
                        case 1: // point
                            {
                                Vector4 vtx = scaledvtxs[face.VtxIndices[0]];
                                int txc = face.TxcIndices[0];

                                if (txc > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc], tcscale));
                                dlpackbw.AddVertexCommand(vtx, lastvtx);
                                if (txc > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc], tcscale));
                                dlpackbw.AddVertexCommand(vtx, vtx);
                                if (txc > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc], tcscale));
                                dlpackbw.AddVertexCommand(vtx, vtx);
                                lastvtx = vtx;
                            }
                            break;

                        case 2: // line
                            {
                                Vector4 vtx1 = scaledvtxs[face.VtxIndices[0]];
                                int txc1 = face.TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.VtxIndices[1]];
                                int txc2 = face.TxcIndices[1];

                                if (txc1 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc1], tcscale));
                                dlpackbw.AddVertexCommand(vtx1, lastvtx);
                                if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc2], tcscale));
                                dlpackbw.AddVertexCommand(vtx2, vtx1);
                                if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc2], tcscale));
                                dlpackbw.AddVertexCommand(vtx2, vtx2);
                                lastvtx = vtx2;
                            }
                            break;

                        case 3: // triangle
                            {
                                Vector4 vtx1 = scaledvtxs[face.VtxIndices[0]];
                                int txc1 = face.TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.VtxIndices[1]];
                                int txc2 = face.TxcIndices[1];
                                Vector4 vtx3 = scaledvtxs[face.VtxIndices[2]];
                                int txc3 = face.TxcIndices[2];

                                if (txc1 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc1], tcscale));
                                dlpackbw.AddVertexCommand(vtx1, lastvtx);
                                //if (m_ZMirror)
                                //{
                                //    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                //     dlpackbw.AddVbwtexCommand(vtx3, vtx1);
                                //    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                //    dlpackbw.AddVbwtexCommand(vtx2, vtx3);
                                //    lastvtx = vtx2;
                                //}
                                //else
                                {
                                    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc2], tcscale));
                                    dlpackbw.AddVertexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc3], tcscale));
                                    dlpackbw.AddVertexCommand(vtx3, vtx2);
                                    lastvtx = vtx3;
                                }
                            }
                            break;

                        case 4: // quad
                            {
                                Vector4 vtx1 = scaledvtxs[face.VtxIndices[0]];
                                int txc1 = face.TxcIndices[0];
                                Vector4 vtx2 = scaledvtxs[face.VtxIndices[1]];
                                int txc2 = face.TxcIndices[1];
                                Vector4 vtx3 = scaledvtxs[face.VtxIndices[2]];
                                int txc3 = face.TxcIndices[2];
                                Vector4 vtx4 = scaledvtxs[face.VtxIndices[3]];
                                int txc4 = face.TxcIndices[3];

                                if (txc1 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc1], tcscale));
                                dlpackbw.AddVertexCommand(vtx1, lastvtx);
                                //if (m_ZMirror)
                                //{
                                //    if (txc4 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                //    dlpackbw.AddVbwtexCommand(vtx4, vtx1);
                                //    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                //    dlpackbw.AddVbwtexCommand(vtx3, vtx4);
                                //    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                //   dlpackbw.AddVbwtexCommand(vtx2, vtx3);
                                //   lastvtx = vtx2;
                                //}
                                //else
                                {
                                    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc2], tcscale));
                                    dlpackbw.AddVertexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc3], tcscale));
                                    dlpackbw.AddVertexCommand(vtx3, vtx2);
                                    if (txc4 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(textCoords[txc4], tcscale));
                                    dlpackbw.AddVertexCommand(vtx4, vtx3);
                                    lastvtx = vtx4;
                                }
                            }
                            break;

                        default: // whatevbw (import as triangle strip)
                            {
                                // todo
                                /*for (int i = 0; i < nvtx - 4; i++)
                                {
                                    Vector4 vtx1 = scaledvtxs[face.m_VtxIndices[i+0]];
                                    int txc1 = face.m_TxcIndices[i + 0];
                                    Vector4 vtx2 = scaledvtxs[face.m_VtxIndices[i + 1]];
                                    int txc2 = face.m_TxcIndices[i + 1];
                                    Vector4 vtx3 = scaledvtxs[face.m_VtxIndices[i + 2]];
                                    int txc3 = face.m_TxcIndices[i + 2];
                                    Vector4 vtx4 = scaledvtxs[face.m_VtxIndices[i + 3]];
                                    int txc4 = face.m_TxcIndices[i + 3];
                                    Vector4 vtx5 = scaledvtxs[face.m_VtxIndices[i + 4]];
                                    int txc5 = face.m_TxcIndices[i + 4];

                                    if (txc1 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc1], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx1, lastvtx);
                                    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx2, vtx1);
                                    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx3, vtx2);

                                    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx3, vtx3);
                                    if (txc2 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc2], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx2, vtx3);
                                    if (txc4 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx4, vtx2);

                                    if (txc3 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc3], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx3, vtx4);
                                    if (txc4 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc4], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx4, vtx3);
                                    if (txc5 > -1) dlpackbw.AddTexCoordCommand(Vector2.Multiply(m_TexCoords[txc5], tcscale));
                                    dlpackbw.AddVbwtexCommand(vtx5, vtx4);


                                    lastvtx = vtx5;
                                }*/
                            }
                            break;
                    }
                }
                dlpackbw.AddCommand(0x41);
                byte[] dlist = dlpackbw.GetDisplayList();
                dl.Add(dlist);
                b++;
            }



            bw.Write((byte)0);
            bw.Write((byte)materials.Count);
            bw.Write((Int16)(16 + 0x18 * materials.Count));

            bw.Write((Int16)0x8);
            bw.Write((Int16)(0x8 + materials.Count * 4 + 4));
            bw.Write(new byte[] { 0x7F, 0x01, 0x00, 0x00 }, 0, 4);
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)0x46);
                bw.Write((Int16)0x1);
            }

            bw.Write((Int16)0x4);
            bw.Write((Int16)(0x4 + 0x4 * materials.Count));
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((UInt32)((16 + 0x18 * materials.Count) + i * 16));
            }
            for (int i = 0; i < materials.Count; i++)
            {
                name = materials.Keys.ToArray()[i] + "_ply";
                if (name.Length > 16)
                {
                    name = name.Remove(16);
                }
                //bw.Write(name, Encoding.ASCII, false);
                bw.Write(Encoding.UTF8.GetBytes(name));
                if (name.Length != 16)
                {
                    bw.Write(new byte[16 - name.Length], 0, 16 - name.Length);
                }
            }
            int pos_ = materials.Count * 16;
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write((Int16)0);
                bw.Write((Int16)16);
                bw.Write((Int16)0xD);
                bw.Write((Int16)0);
                bw.Write((Int32)pos_);
                bw.Write((Int32)dl[i].Length);
                pos_ += dl[i].Length - 16;
            }
            for (int i = 0; i < materials.Count; i++)
            {
                bw.Write(dl[i], 0, dl[i].Length);
            }


            //Write sizes
            curpos = bw.BaseStream.Position;
            bw.BaseStream.Position = offsetpos;
            bw.Write((Int32)curpos - 0x44);
            bw.BaseStream.Position = offsetpos + 16;
            bw.Write((Int32)curpos - 0x44);
            bw.BaseStream.Position = 8;
            bw.Write((Int32)curpos);
            bw.BaseStream.Position = 0x18;
            bw.Write((Int32)curpos - 0x14);
            bw.Close();
        }

        private void LoadObjModel(string file)
        {
            StreamReader sr = new StreamReader(file);

            // Set up
            materials.Clear();
            textures.Clear();
            verticies.Clear();
            textCoords.Clear();
            normals.Clear();

            string currentMaterial = "";
            CultureInfo usa = new CultureInfo("en-US");

            string line;
            while ((line = sr.ReadLine()) != null)
            {
                line = line.Trim();

                // Skip stuff we don't need
                if (line.Length < 1) continue;
                if (line.StartsWith("#")) continue;

                // Split into pieces.
                string[] parts = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 1) continue;

                // Parse
                switch (parts[0])
                {
                    case "mtllib": // Material library
                        {
                            string filepath = line.Substring(parts[0].Length + 1).Trim();
                            LoadObjMaterial(file.Replace(Path.GetFileName(file), "") + filepath, file.Replace(Path.GetFileName(file), ""));
                        }
                        break;
                    case "usemtl": // Material name
                        if (parts.Length < 2) continue;
                        currentMaterial = parts[1];
                        break;
                    case "v": // Vertex
                        {
                            if (parts.Length < 4) continue;
                            float x = float.Parse(parts[1], usa);
                            float y = float.Parse(parts[2], usa);
                            float z = float.Parse(parts[3], usa);
                            float w = 1f; // TODO: Parse vector4?

                            verticies.Add(new Vector4(x, y, z, w));
                        }
                        break;
                    case "vt": // Texture Coordinate
                        {
                            if (parts.Length < 2) continue;
                            float s = float.Parse(parts[1], usa);
                            float t = (parts.Length < 3) ? 0f : float.Parse(parts[2], usa);
                            textCoords.Add(new Vector2(s, t));
                        }
                        break;
                    case "vn": // Normal
                        {
                            if (parts.Length < 4) continue;
                            float x = float.Parse(parts[1], usa);
                            float y = float.Parse(parts[2], usa);
                            float z = float.Parse(parts[3], usa);

                            Vector3 v = new Vector3(x, y, z);
                            v.Normalize(); // Hence the name
                            normals.Add(v);
                        }
                        break;
                    case "f": // Face
                        {
                            if (parts.Length < 4) continue;
                            int nvtx = parts.Length - 1;

                            MaterialDef mat = (MaterialDef)materials[currentMaterial];
                            FaceDef face = new FaceDef();
                            face.MaterialName = currentMaterial;
                            face.VtxIndices = new int[nvtx];
                            face.TxcIndices = new int[nvtx];
                            face.NrmIndices = new int[nvtx];

                            for (int i = 0; i < nvtx; i++)
                            {
                                string vtx = parts[i + 1];
                                string[] idxs = vtx.Split(new char[] { '/' });

                                face.VtxIndices[i] = int.Parse(idxs[0]) - 1;
                                face.TxcIndices[i] = (mat.HasTextures && idxs.Length >= 2 && idxs[1].Length > 0)
                                    ? (int.Parse(idxs[1]) - 1) : -1;
                                face.NrmIndices[i] = (idxs.Length >= 3) ? (int.Parse(idxs[2]) - 1) : -1;
                            }

                            mat.Faces.Add(face);
                        }
                        break;
                }
            }
        }

        private void LoadObjMaterial(string file, string modelPath)
        {
            StreamReader sr = new StreamReader(file);

            string curmaterial = "";
            CultureInfo usahax = new CultureInfo("en-US");

            string curline;
            while ((curline = sr.ReadLine()) != null)
            {
                curline = curline.Trim();

                // skip empty lines and comments
                if (curline.Length < 1) continue;
                if (curline[0] == '#')
                {
                    if (curline.Contains("SketchUp"))
                        sketchupHack = true;

                    continue;
                }

                string[] parts = curline.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 1) continue;

                switch (parts[0])
                {
                    case "newmtl": // new material definition
                        {
                            if (parts.Length < 2) continue;
                            curmaterial = parts[1];

                            MaterialDef mat = new MaterialDef();
                            mat.Faces = new List<FaceDef>();
                            mat.DiffuseColor = Color.White;
                            mat.Opacity = 255;
                            mat.HasTextures = false;
                            mat.DiffuseMapName = "";
                            mat.DiffuseMapID = 0;
                            mat.DiffuseMapSize = new Vector2(0f, 0f);
                            materials.Add(curmaterial, mat);
                        }
                        break;

                    case "d":
                    case "Tr": // opacity
                        {
                            if (parts.Length < 2) continue;
                            float o = float.Parse(parts[1], usahax);
                            if (sketchupHack)
                                o *= 255;

                            MaterialDef mat = (MaterialDef)materials[curmaterial];
                            mat.Opacity = Math.Max(0, Math.Min(255, (int)(o * 255)));
                        }
                        break;

                    case "Kd": // diffuse color
                        {
                            if (parts.Length < 4) continue;
                            float r = float.Parse(parts[1], usahax);
                            float g = float.Parse(parts[2], usahax);
                            float b = float.Parse(parts[3], usahax);
                            Color col;
                            try
                            {
                                col = Color.FromArgb((int)(r * 255), (int)(g * 255), (int)(b * 255));
                            }
                            catch { col = Color.Black; }

                            MaterialDef mat = (MaterialDef)materials[curmaterial];
                            mat.DiffuseColor = col;
                        }
                        break;

                    case "map_Kd":
                    case "mapKd": // diffuse map (texture)
                        {
                            string texname = curline.Substring(parts[0].Length + 1).Trim();
                            Bitmap tex = new Bitmap(modelPath + texname);

                            int width = 8, height = 8;
                            while (width < tex.Width) width *= 2;
                            while (height < tex.Height) height *= 2;

                            // cheap resizing for textures whose dimensions aren't power-of-two
                            if ((width != tex.Width) || (height != tex.Height))
                            {
                                Bitmap newbmp = new Bitmap(width, height);
                                Graphics g = Graphics.FromImage(newbmp);
                                g.DrawImage(tex, new Rectangle(0, 0, width, height));
                                tex = newbmp;
                            }

                            MaterialDef mat = (MaterialDef)materials[curmaterial];
                            mat.HasTextures = true;

                            byte[] map = new byte[tex.Width * tex.Height * 4];
                            for (int y = 0; y < tex.Height; y++)
                            {
                                for (int x = 0; x < tex.Width; x++)
                                {
                                    Color pixel = tex.GetPixel(x, y);
                                    int pos = ((y * tex.Width) + x) * 4;

                                    map[pos] = pixel.B;
                                    map[pos + 1] = pixel.G;
                                    map[pos + 2] = pixel.R;
                                    map[pos + 3] = pixel.A;
                                }
                            }
                            //System.Drawing.Imaging.BitmapData lol = tex.LockBits(new Rectangle(0, 0, tex.Width, tex.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                            //System.Runtime.InteropServices.Marshal.Copy(lol.Scan0, map, 0, tex.Width * tex.Height * 4);

                            string imghash = HexString(md5.ComputeHash(map));
                            if (textures.ContainsKey(imghash))
                            {
                                MaterialDef mat2 = textures[imghash];
                                mat.DiffuseMapName = mat2.DiffuseMapName;
                                mat.DiffuseMapID = mat2.DiffuseMapID;
                                mat.DiffuseMapSize = mat2.DiffuseMapSize;
                                break;
                            }

                            mat.DiffuseMapName = texname;
                            textures.Add(imghash, mat);

                            mat.DiffuseMapSize.X = tex.Width;
                            mat.DiffuseMapSize.Y = tex.Height;
                        }
                        break;
                }
            }

            sr.Close();
        }

        private string HexString(byte[] buffer)
        {
            string result = "";
            foreach (byte b in buffer) result += b.ToString("X2");
            return result;
        }

        private class FaceDef
        {
            public int[] VtxIndices;
            public int[] TxcIndices;
            public int[] NrmIndices;
            public string MaterialName;
        }

        private class MaterialDef
        {
            public List<FaceDef> Faces;

            public Color DiffuseColor;
            public int Opacity;

            public bool HasTextures;


            //public byte[] m_DiffuseMap;
            public string DiffuseMapName;
            public int DiffuseMapID;
            public Vector2 DiffuseMapSize;

            // haxx
            public float TexCoordScale;
        }
    }

    public class GXDisplayListPacker
    {
        public GXDisplayListPacker()
        {
            m_CommandList = new List<GXCommand>();
        }

        public void AddCommand(byte _cmd, uint[] _params)
        {
            m_CommandList.Add(new GXCommand(_cmd, _params));
        }
        public void AddCommand(byte _cmd)
        {
            m_CommandList.Add(new GXCommand(_cmd, new uint[] { }));
        }
        public void AddCommand(byte _cmd, uint param1)
        {
            m_CommandList.Add(new GXCommand(_cmd, new uint[] { param1 }));
        }
        public void AddCommand(byte _cmd, uint param1, uint param2)
        {
            m_CommandList.Add(new GXCommand(_cmd, new uint[] { param1, param2 }));
        }

        public void AddVertexCommand(Vector4 _vtx, Vector4 _prev)
        {
            if (_prev.W == 12345678f)
            {
                AddVertexCommand(_vtx);
                return;
            }

            Vector4 vtx = Vector4.Multiply(_vtx, 4096f);
            Vector4 prev = Vector4.Multiply(_prev, 4096f);

            if (Math.Abs(vtx.X - prev.X) < 1f)
            {
                uint param = (uint)(((ushort)(short)vtx.Y) | (((ushort)(short)vtx.Z) << 16));
                AddCommand(0x27, param);
            }
            else if (Math.Abs(vtx.Y - prev.Y) < 1f)
            {
                uint param = (uint)(((ushort)(short)vtx.X) | (((ushort)(short)vtx.Z) << 16));
                AddCommand(0x26, param);
            }
            else if (Math.Abs(vtx.Z - prev.Z) < 1f)
            {
                uint param = (uint)(((ushort)(short)vtx.X) | (((ushort)(short)vtx.Y) << 16));
                AddCommand(0x25, param);
            }
            else
                AddVertexCommand(_vtx);
        }
        public void AddVertexCommand(Vector4 _vtx)
        {
            Vector4 vtx = Vector4.Multiply(_vtx, 4096f);

            uint x = (uint)vtx.X;
            uint y = (uint)vtx.Y;
            uint z = (uint)vtx.Z;
            if (((x & 0x3F) == 0) && ((y & 0x3F) == 0) && ((z & 0x3F) == 0))
            {
                uint param = (uint)((((ushort)(short)x) >> 6) | (((ushort)(short)y) << 4) | (((ushort)(short)z) << 14));
                AddCommand(0x24, param);
            }
            else
            {
                uint param1 = (uint)(((ushort)(short)x) | (((ushort)(short)y) << 16));
                uint param2 = (uint)((ushort)(short)z);
                AddCommand(0x23, param1, param2);
            }
        }

        public void AddTexCoordCommand(Vector2 txc)
        {
            short s = (short)(txc.X * 16);
            short t = (short)(txc.Y * 16);
            uint param = (uint)(((ushort)s) | ((ushort)t) << 16);
            AddCommand(0x22, param);
        }

        public void AddColorCommand(Color color)
        {
            AddCommand(0x20, Actions.ColorToBGR15(color));
        }

        public void ClearCommands()
        {
            m_CommandList.Clear();
        }

        public byte[] GetDisplayList()
        {
            List<byte> ret = new List<byte>();
            int numcmd = (m_CommandList.Count + 3) & ~3;

            for (int i = m_CommandList.Count; i < numcmd; i++)
                AddCommand(0x00);

            for (int i = 0; i < numcmd; i += 4)
            {
                for (int j = 0; j < 4; j++)
                    ret.Add(m_CommandList[i + j].m_Command);

                for (int j = 0; j < 4; j++)
                {
                    foreach (uint param in m_CommandList[i + j].m_Parameters)
                    {
                        ret.Add((byte)(param & 0xFF));
                        ret.Add((byte)((param >> 8) & 0xFF));
                        ret.Add((byte)((param >> 16) & 0xFF));
                        ret.Add((byte)(param >> 24));
                    }
                }
            }

            return ret.ToArray();
        }


        public struct GXCommand
        {
            public byte m_Command;
            public uint[] m_Parameters;

            public GXCommand(byte _cmd, uint[] _params)
            {
                m_Command = _cmd;
                m_Parameters = _params;
            }
        }

        public List<GXCommand> m_CommandList;
    }
}
